package com.cucn.ingmall.search.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.cucn.common.to.es.SkuEsModel;
import com.cucn.common.utils.R;
import com.cucn.ingmall.search.config.IngmallElasticSearchConfig;
import com.cucn.ingmall.search.constant.EsConstant;
import com.cucn.ingmall.search.dto.SearchResponseDTO;
import com.cucn.ingmall.search.feign.ProductFeignService;
import com.cucn.ingmall.search.service.MallSearchService;
import com.cucn.ingmall.search.vo.AttrResponseVO;
import com.cucn.ingmall.search.vo.BrandVO;
import com.cucn.ingmall.search.vo.SearchRequestParamVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 商城检索服务实现
 *
 * @author KouChaoJie
 * @since: 2021/12/28 15:53
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Resource
    private ProductFeignService productFeignService;

    /**
     * 检索商品
     * 从Es中检索
     * 动态构建出查询需要的DSL语句
     *
     * @param searchRequestParamVO 检索的所有参数
     * @return 返回检索的结果
     */
    @Override
    public SearchResponseDTO search(SearchRequestParamVO searchRequestParamVO) {
        //返回结果
        SearchResponseDTO searchResponseDTO = null;

        //创建es检索请求
        SearchRequest searchRequest = buildSearchRequest(searchRequestParamVO);

        try {
            //2.执行检索请求
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, IngmallElasticSearchConfig.COMMON_OPTIONS);

            //3.分析响应数据封装成我们需要的格式
            searchResponseDTO = buildSearchReqsponse(searchResponse, searchRequestParamVO);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return searchResponseDTO;
    }

    /**
     * 封装响应数据
     *
     * @param searchResponse     es检索响应数据
     * @param searchRequestParam 检索请求的参数
     * @return es检索返参
     */
    private SearchResponseDTO buildSearchReqsponse(SearchResponse searchResponse, SearchRequestParamVO searchRequestParam) {
        //构建返参
        SearchResponseDTO searchResponseDTO = new SearchResponseDTO();

        //命中hit
        SearchHits hits = searchResponse.getHits();

        //查询获取
        //1.封装所有商品
        List<SkuEsModel> productList = new ArrayList<>();
        if (ObjectUtil.isNotEmpty(hits) && hits.getHits().length > 0) {
            for (SearchHit searchHit : hits.getHits()) {
                String sourceAsString = searchHit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                //关键字高亮
                if (StringUtils.isNotEmpty(searchRequestParam.getKeyword())) {
                    HighlightField skuTitle = searchHit.getHighlightFields().get("skuTitle");
                    String string = skuTitle.getFragments()[0].string();
                    skuEsModel.setSkuTitle(string);
                }
                productList.add(skuEsModel);
            }
        }
        searchResponseDTO.setProducts(productList);

        //聚合获取
        Aggregations aggregations = searchResponse.getAggregations();
        List<SearchResponseDTO.AttrVO> resultAttrList = new ArrayList<>();
        //2.所有商品涉及到的所有属性信息
        ParsedNested attrAgg = aggregations.get("attr_agg");
        //获取属性id子聚合
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResponseDTO.AttrVO attrVO = new SearchResponseDTO.AttrVO();
            Long attrId = bucket.getKeyAsNumber().longValue();
            attrVO.setAttrId(attrId);

            //获取属性名子聚合
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVO.setAttrName(attrName);

            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValue = attrValueAgg.getBuckets().stream().map(attrValueBucket -> attrValueBucket.getKeyAsString()).collect(Collectors.toList());
            attrVO.setAttrValue(attrValue);

            resultAttrList.add(attrVO);
        }
        searchResponseDTO.setAttrs(resultAttrList);

        //3.所有商品涉及的品牌信息
        List<SearchResponseDTO.BrandVO> resultBrandList = new ArrayList<>();
        //获取品牌聚合
        ParsedLongTerms brandAgg = aggregations.get("brand_agg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResponseDTO.BrandVO brandVO = new SearchResponseDTO.BrandVO();
            Long brandId = bucket.getKeyAsNumber().longValue();
            brandVO.setBrandId(brandId);

            //获取品牌图片的聚合
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            brandVO.setBrandImg(brandImg);

            //品牌名字
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVO.setBrandName(brandName);

            resultBrandList.add(brandVO);
        }
        searchResponseDTO.setBrands(resultBrandList);

        //4.当前商品涉及到的所有分类信息
        List<SearchResponseDTO.CatalogVO> resultCatalogList = new ArrayList<>();
        //拿到分类聚合
        ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResponseDTO.CatalogVO catalogVO = new SearchResponseDTO.CatalogVO();
            //获取buket下的key
            String catalogId = bucket.getKeyAsString();
            catalogVO.setCatalogId(Long.parseLong(catalogId));

            //获取子聚合
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogVO.setCatalogName(catalogName);

            resultCatalogList.add(catalogVO);
        }
        searchResponseDTO.setCatalogs(resultCatalogList);

        //5.分页信息-当前页码-总记录数-总页码
        //总记录数
        long total = hits.getTotalHits().value;
        searchResponseDTO.setTotal(total);
        //总页码--计算
        int totalPages = (total % EsConstant.PAGE_SIZE == 0 ? (int) total / EsConstant.PAGE_SIZE : (int) total / EsConstant.PAGE_SIZE + 1);
        searchResponseDTO.setTotalPages(totalPages);
        //当前页码
        searchResponseDTO.setPageNum(searchRequestParam.getPageNum());

        List<Integer> pageNavas = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageNavas.add(i);
        }
        searchResponseDTO.setPageNavs(pageNavas);

        //构建面包屑导航
        //attrs=11_麒麟9000
        //操作都是在对面包屑导航的地址操作,所以和正常检索的属性叠加无关
        if (!CollectionUtils.isEmpty(searchRequestParam.getAttrs())) {
            List<SearchResponseDTO.NavVO> navVOList = searchRequestParam.getAttrs().stream().map(item -> {
                SearchResponseDTO.NavVO navVO = new SearchResponseDTO.NavVO();
                //分割属性字符串
                String[] attrArr = item.split("_");
                navVO.setNavValue(attrArr[1]);
                //远程调用商品服务根据属性id查询属性名称
                R r = productFeignService.attrInfo(Long.valueOf(attrArr[0]));

                //方便面包屑导航条件筛选联动构建
                searchResponseDTO.getAttrIds().add(Long.valueOf(attrArr[0]));

                if (r.getCode() == 0) {
                    AttrResponseVO attr = r.getData("attr", new TypeReference<AttrResponseVO>() {
                    });
                    navVO.setNavName(attr.getAttrName());
                } else {
                    //如果调用失败那就使用属性的id当名字封装
                    navVO.setNavName(attrArr[0]);
                }

                //拿到所有的查询条件、去掉当前的条件,需要转换中文编码
                String queryString = replaceQueryString(searchRequestParam, item, "attrs");
                //拼新url串
                navVO.setLink("http://search.koukoucoding.xn--fiqs8s/list.html?" + queryString);
                return navVO;
            }).collect(Collectors.toList());
            searchResponseDTO.setNav(navVOList);
        }

        //品牌分类也上面包屑导航
        if (!CollectionUtils.isEmpty(searchRequestParam.getBrandId()) || !CollectionUtils.isEmpty(searchRequestParam.getBrandId())) {
            SearchResponseDTO.NavVO navVO = new SearchResponseDTO.NavVO();
            //获取属性处理过的面包屑或默认面包屑
            List<SearchResponseDTO.NavVO> navVOList = searchResponseDTO.getNav();

            //远程调用查询分类信息
            navVO.setNavName("品牌");
            R r = productFeignService.brandInfos(searchRequestParam.getBrandId());
            if (r.getCode() == 0) {
                List<BrandVO> brandList = r.getData("brand", new TypeReference<List<BrandVO>>() {
                });
                StringBuffer brandStringBuffer = new StringBuffer();
                String queryString = null;
                for (BrandVO brandVO : brandList) {
                    brandStringBuffer.append(brandVO.getName()).append(";");
                    //编码、替换
                    queryString = replaceQueryString(searchRequestParam, String.valueOf(brandVO.getBrandId()), "brandId");

                }
                navVO.setLink("http://search.koukoucoding.xn--fiqs8s/list.html?" + queryString);
                navVO.setNavValue(brandStringBuffer.toString());
            }
            navVOList.add(navVO);
        }

        //KouTODO:2022/1/4 10:42 Kou 分类,不需要导航取消

        return searchResponseDTO;
    }

    /**
     * 去掉当前的条件,需要转换中文编码
     *
     * @param searchRequestParam 检索条件
     * @param value              替换并编码的属性
     * @return 经过操作后的查询字符串
     */
    private String replaceQueryString(SearchRequestParamVO searchRequestParam, String value, String key) {
        String encode = null;
        try {
            encode = URLEncoder.encode(value, "UTF-8");
            encode = encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String queryString = searchRequestParam.getQueryString().replace("&" + key + "=" + encode, "");
        return queryString;
    }

    /**
     * 构造检索请求
     * 模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存，排序，页码，高亮），聚合分析
     *
     * @return 检索请求体
     */
    private SearchRequest buildSearchRequest(SearchRequestParamVO searchParam) {
        //构建dsl语句
        SearchSourceBuilder builder = new SearchSourceBuilder();

        /*
        1.关键字模糊匹配，过滤(属性，分类，品牌，价格区间，库存)
         */
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1 must
        if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery(EsConstant.SKU_TITLE, searchParam.getKeyword()));
        }

        //1.2 filter
        //分类id
        if (searchParam.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery(EsConstant.CATALOG_ID, searchParam.getCatalog3Id()));
        }
        //品牌id
        if (searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery(EsConstant.BRAND_ID, searchParam.getBrandId()));
        }
        //属性id 与 属性值
        if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0) {
            //遍历attrs集合,分析出属性的id与值  attrs=1_6g:8g  分割字符串
            for (String attr : searchParam.getAttrs()) {
                //嵌入式处理
                BoolQueryBuilder nestBollQuery = QueryBuilders.boolQuery();
                String[] attrArr = attr.split("_");
                String attrId = attrArr[0];
                String[] attrValueArr = attrArr[1].split(":");
                nestBollQuery.must(QueryBuilders.termQuery(EsConstant.ATTRS_ATTRID, attrId));
                nestBollQuery.must(QueryBuilders.termsQuery(EsConstant.ATTRS_ATTRVALUE, attrValueArr));
                //每一个都必须生成一个nested
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery(EsConstant.ATTRS, nestBollQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        //库存
        if (searchParam.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery(EsConstant.HAS_STOCK, searchParam.getHasStock() == 1));
        }
        //价格区间
        if (StringUtils.isNotEmpty(searchParam.getSkuPrice())) {
            String[] pricesArr = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(EsConstant.SKU_PRICE);
            //价格区间,可能为空串
            if (pricesArr.length == 2) {
                rangeQuery.gte(pricesArr[0]).lte(pricesArr[1]);
            } else if (pricesArr.length == 1) {
                rangeQuery.gte(pricesArr[0]);
            }
            boolQuery.filter(rangeQuery);
        }

        builder.query(boolQuery);

        /*
        2.排序，页码，高亮
         */
        //2.1 排序  sort=saleCount_asc/desc 销量排序
        if (StringUtils.isNotEmpty(searchParam.getSort())) {
            String[] sortArr = searchParam.getSort().split("_");
            SortOrder order = EsConstant.ASC.equalsIgnoreCase(sortArr[1]) ? SortOrder.ASC : SortOrder.DESC;
            builder.sort(sortArr[0], order);
        }
        //2.2 分页
        //from=(pageNum-1)*size
        builder.from((searchParam.getPageNum() - 1) * EsConstant.PAGE_SIZE);
        builder.size(EsConstant.PAGE_SIZE);

        //2.3高亮
        if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field(EsConstant.SKU_TITLE);
            highlightBuilder.preTags("<b style='color:red'>").postTags("</b>");
            builder.highlighter(highlightBuilder);
        }

        /*
        聚合分析
         */
        //品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg");
        brandAgg.field(EsConstant.BRAND_ID).size(50);
        //品牌子聚合
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field(EsConstant.BRAND_NAME).size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field(EsConstant.BRAND_IMG).size(1));
        builder.aggregation(brandAgg);

        //分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg");
        catalogAgg.field(EsConstant.CATALOG_ID).size(20);
        //分类子聚合
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field(EsConstant.CATALOG_NAME).size(1));
        builder.aggregation(catalogAgg);

        //属性聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", EsConstant.ATTRS);
        //子聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field(EsConstant.ATTRS_ATTRID);
        //孙聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field(EsConstant.ATTRS_ATTRNAME).size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field(EsConstant.ATTRS_ATTRVALUE).size(50));
        attrAgg.subAggregation(attrIdAgg);

        //构建聚合
        builder.aggregation(attrAgg);

        String builderStr = builder.toString();
        log.info("dsl语句构建:{}", builderStr);

        //索引
        String[] index = new String[]{EsConstant.INGMALL_PRODUCT_INDEX};
        SearchRequest searchRequest = new SearchRequest(index, builder);

        return searchRequest;
    }
}
